package com.bestcem.xm.txtanls.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.bestcem.xm.common.core.domain.web.ServiceResult;
import com.bestcem.xm.common.core.uitls.GRpcUtil;
import com.bestcem.xm.qdes.grpc.v1.services.OpenQuestion;
import com.bestcem.xm.txtanls.dao.AnalysisProjectDao;
import com.bestcem.xm.txtanls.dao.SurveyTxtanlsFieldDao;
import com.bestcem.xm.txtanls.entity.pojo.ProjectDO;
import com.bestcem.xm.txtanls.entity.pojo.ProjectFieldDO;
import com.bestcem.xm.txtanls.entity.pojo.SurveyTxtanlsFieldDO;
import com.bestcem.xm.txtanls.enums.AnalysisStatusEnum;
import com.bestcem.xm.txtanls.grpc.client.BaseQdesGrpcService;
import com.bestcem.xm.txtanls.grpc.service.param.survey.GetSurveyAnalysisProjectParam;
import com.bestcem.xm.txtanls.grpc.service.param.survey.SurveyFieldParam;
import com.bestcem.xm.txtanls.grpc.service.param.survey.UpdateOpenOptionParam;
import com.bestcem.xm.txtanls.service.AnalysisProjectService;
import com.bestcem.xm.txtanls.service.SurveyService;
import com.bestcem.xm.txtanls.service.dto.analysis.AnalysisProjectDTO;
import com.bestcem.xm.txtanls.service.dto.survey.SurveyDTO;
import com.bestcem.xm.txtanls.util.AnalysisUtil;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import javax.validation.ConstraintViolation;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 问卷分析项目相关业务接口实现
 *
 * @author XiaoYunSong <ys.xiao@idiaoyan.com>
 * @version v1.0
 * @date 2021/5/8
 */
@Slf4j
@Service
public class SurveyServiceImpl implements SurveyService {
    /**
     * 问卷分析项目名称最大长度
     */
    public static final int NAME_LENGTH = 15;
    private static final Logger EMAIL_LOG = LoggerFactory.getLogger("EmailErrorLog");
    /**
     * 问卷项目初始化时间字段默认填充
     */
    private static final String NULL = "Null";

    @Resource
    private AnalysisUtil analysisUtil;
    @Resource
    private AnalysisProjectDao analysisProjectDao;
    @Resource
    private SurveyTxtanlsFieldDao surveyTxtanlsFieldDao;
    @Resource
    private AnalysisProjectService analysisProjectService;
    @Resource
    private BaseQdesGrpcService baseQdesGrpcService;

    @Override
    public ServiceResult<List<OpenQuestion>> getSurveyOpenOptions(String surveyId) {
        List<OpenQuestion> openOptionDTOList = baseQdesGrpcService.getProjectAllOpenOption(surveyId);
        return ServiceResult.success(openOptionDTOList);
    }

    @Override
    public ServiceResult<AnalysisProjectDTO> getSurveyAnalysisProject(GetSurveyAnalysisProjectParam param) {
        // 参数校验
        Set<ConstraintViolation<Object>> violations = GRpcUtil.validateParam(param);
        if (!CollectionUtils.isEmpty(violations)) {
            ConstraintViolation<Object> violation = violations.iterator().next();
            return ServiceResult.fail(violation.getMessage());
        }

        ProjectDO entity = analysisProjectDao.findSurveyAnalysisProject(param.getSurveyId());
        if (Objects.isNull(entity)) {
            entity = new ProjectDO();
            entity.setOrgId(param.getOrgId());
            entity.setCreator(param.getUserId());
            SurveyDTO surveyDTO = baseQdesGrpcService.getSurveyProject(param.getSurveyId());
            if (Objects.isNull(surveyDTO)) {
                return ServiceResult.fail("问卷分析项目创建失败");
            }
            String name = surveyDTO.getTitle();
            if (name.length() > NAME_LENGTH) {
                name = name.substring(0, NAME_LENGTH);
            }
            entity.setName(name);
            entity.setTableId(param.getSurveyId());
            entity.setTableName(name);
            entity.setTimeFieldId(NULL);
            entity.setTimeFieldName("");
            entity.setStatus(AnalysisStatusEnum.ACTIVE.getIndex());
            entity.setSurveyId(param.getSurveyId());
            entity = analysisProjectDao.insertProject(entity);
            if (Objects.isNull(entity.getId())) {
                return ServiceResult.fail("问卷分析项目创建失败");
            }

            try {
                // 初始化项目联动信息
                if (!analysisUtil.initAnalysisProject(entity.getId(), entity.getOrgId(),null)) {
                    analysisUtil.rollbackProject(entity.getId());
                    return ServiceResult.fail("问卷分析项目创建失败");
                }
            } catch (Exception e) {
                log.error("[Txtanls] 初始化项目联动信息异常; ErrorMsg:{}", e.getMessage(), e);
                analysisUtil.rollbackProject(entity.getId());
                return ServiceResult.fail("问卷分析项目创建失败");
            }
        }

        if (NULL.equals(entity.getTimeFieldId())) {
            // 问卷分析项目为初始化时间字段, 初始化插入问卷项目的时间分析字段到 TXTANLS_SURVEYTXTANLSFIELDS
            SurveyTxtanlsFieldDO timeField = surveyTxtanlsFieldDao.insertTimeField(entity.getId(), param.getSurveyId());
            if (log.isInfoEnabled()) {
                log.info("[Txtanls] 问卷项目 {} 时间字段信息 {}", entity.getId(), timeField);
            }
            if (Objects.nonNull(timeField)) {
                int updateResult = analysisProjectDao.updateProjectTimeField(entity.getId(), timeField.getId(), "time");
                if (updateResult == 0) {
                    log.error("[Txtanls] 问卷项目 {} 时间字段信息 {} 更新失败", entity.getId(), timeField);
                    EMAIL_LOG.error("[Txtanls] 问卷项目 {} 时间字段信息 {} 更新失败", entity.getId(), timeField);
                }
            }
        }
        // 查找最新的项目信息
        entity = analysisProjectDao.findById(entity.getId());
        AnalysisProjectDTO dto = AnalysisProjectDTO.analysisEntityToDTO(entity);
        // 设置项目对应的用户信息
        dto = analysisUtil.setUserInfo(dto);
        // 设置项目的兜底训练状态信息,防止对应字段为空
        dto = analysisUtil.setProjectTrainInfo(dto);
        // 问卷训练项目将字段信息中的开放选项id替换为开放选项标题
        if (!CollectionUtils.isEmpty(dto.getTextFieldList())) {
            dto = analysisUtil.setSurveyProjectFiledInfo(dto);
        }
        return ServiceResult.success(dto);
    }

    @Override
    public ServiceResult<AnalysisProjectDTO> updateOpenOptions(UpdateOpenOptionParam param) {

        ProjectDO projectEntity = analysisProjectDao.findById(param.getProjectId());
        if (Objects.isNull(projectEntity)) {
            return ServiceResult.fail("项目不存在");
        }

        List<SurveyFieldParam> textFieldParamList = param.getTextFields();
        List<SurveyTxtanlsFieldDO> fieldEntities = surveyTxtanlsFieldDao.findProjectAllTextFiled(param.getProjectId());
        // 原有字段
        Map<String, SurveyTxtanlsFieldDO> oldEntityMap =
                fieldEntities.stream().collect(Collectors.toMap(SurveyTxtanlsFieldDO::getSurveyFieldName, self -> self, (prev, now) -> prev));
        // 更新字段
        Map<String, SurveyFieldParam> newFieldMap =
                textFieldParamList.stream().collect(Collectors.toMap(SurveyFieldParam::getId, self -> self, (prev, now) -> prev));
        // 旧数据中训练字段对应开放选项id集合
        Set<String> oldFieldIds = fieldEntities.stream().map(SurveyTxtanlsFieldDO::getSurveyFieldName).collect(Collectors.toSet());
        // 新数据中训练字段对应开放选项id集合
        Set<String> newFieldIds = textFieldParamList.stream().map(SurveyFieldParam::getId).collect(Collectors.toSet());
        // 求交集
        Set<String> intersectionIds = Sets.intersection(oldFieldIds, newFieldIds);
        // 待删除的字段对应开放选项id
        Set<String> deleteFieldIds = oldFieldIds.stream().filter(oldFieldId -> !intersectionIds.contains(oldFieldId)).collect(Collectors.toSet());
        // 待插入的字段对应开放选项id
        Set<String> insertFieldIds = newFieldIds.stream().filter(newFieldId -> !intersectionIds.contains(newFieldId)).collect(Collectors.toSet());
        // 记录 删除/插入的字段的返回id, 用于更新文本分析项目中的训练字段属性
        Set<String> deletedIds = new HashSet<>();
        Map<String, String> insertedIds = new HashMap<>();
        // 删除失效的字段
        for (String deleteFieldId : deleteFieldIds) {
            String deletedId = oldEntityMap.get(deleteFieldId).getId();
            boolean deleteResult = surveyTxtanlsFieldDao.deleteById(deletedId);
            if (!deleteResult) {
                log.error("[Txtanls] 问卷分析项目字段 {} 删除失败; projectId:{}", deleteFieldId, projectEntity.getId());
                EMAIL_LOG.error("[Txtanls] 问卷分析项目字段 {} 删除失败; projectId:{}", deleteFieldId, projectEntity.getId());
                continue;
            }
            deletedIds.add(deletedId);
        }
        // 添加新增的字段
        for (String insertFieldId : insertFieldIds) {
            SurveyTxtanlsFieldDO insertedEntity = surveyTxtanlsFieldDao.
                    insertTextField(projectEntity.getId(), projectEntity.getSurveyId(), insertFieldId, newFieldMap.get(insertFieldId).getTitle());
            if (Objects.isNull(insertedEntity)) {
                log.error("[Txtanls] 问卷分析项目字段 {} 插入失败; projectId:{}", insertFieldId, projectEntity.getId());
                EMAIL_LOG.error("[Txtanls] 问卷分析项目字段 {} 插入失败; projectId:{}", insertFieldId, projectEntity.getId());
                continue;
            }
            insertedIds.put(insertedEntity.getId(), insertFieldId);
        }
        /* 更新问卷对应的文本分析项目 */
        // 未失效的字段
        List<ProjectFieldDO> textFields = new ArrayList<>();
        if (!CollectionUtils.isEmpty(projectEntity.getTextFieldList())) {
            for (ProjectFieldDO textField : projectEntity.getTextFieldList()) {
                if (!deletedIds.contains(textField.getId())) {
                    textFields.add(textField);
                }
            }
        }
        // 新增的字段
        if (!CollectionUtils.isEmpty(insertedIds)) {
            insertedIds.forEach((id, fieldId) -> textFields.add(new ProjectFieldDO(id, fieldId, null)));
        }

        int updateResult = analysisProjectDao.updateProjectFieldList(projectEntity.getId(), textFields);
        if (updateResult == 0) {
            log.error("[Txtanls] 参与分析的开放选项更新失败; projectId:{}", projectEntity.getId());
            EMAIL_LOG.error("[Txtanls] 参与分析的开放选项更新失败; projectId:{}", projectEntity.getId());
            return ServiceResult.fail("参与分析的开放选项更新失败");
        }

        // 更新项目状态为 CREATED, 触发异步任务
        analysisUtil.updateProjectStatus(projectEntity.getId(), AnalysisStatusEnum.CREATED);

        ProjectDO newProjectEntity = analysisProjectDao.findById(projectEntity.getId());
        // 文本分析1.2新增
        Boolean retrain = isRetrainProject(projectEntity, newProjectEntity);
        if (retrain) {
            // 删除项目训练信息
            analysisProjectService.deleteProjectTrainInfo(projectEntity.getId());
            // 开启重新训练
            analysisUtil.retrainProject(newProjectEntity);
        }

        AnalysisProjectDTO dto = AnalysisProjectDTO.analysisEntityToDTO(newProjectEntity);
        dto = analysisUtil.setUserInfo(dto);
        dto = analysisUtil.setProjectTrainInfo(dto);
        if (!CollectionUtils.isEmpty(dto.getTextFieldList())) {
            dto = analysisUtil.setSurveyProjectFiledInfo(dto);
        }
        return ServiceResult.success(dto);
    }

    /**
     * 对比传递的参数和数据库中存储的数据，判断是否重新训练
     *
     * @param srcProject 前端传递的参数
     * @param project    文本分析项目
     * @return Boolean 是否重新训练
     * @author jy.zhao
     * @date 2021/11/24 15:25
     **/
    public Boolean isRetrainProject(ProjectDO srcProject, ProjectDO project) {
        // 时间字段id不相等，重新训练
        if (!StrUtil.equals(project.getTimeFieldId(), srcProject.getTimeFieldId())) {
            return true;
        }
        // 数据源表id不相等，重新训练
        if (!StrUtil.equals(project.getTableId(), srcProject.getTableId())) {
            return true;
        }
        List<ProjectFieldDO> srcTextFieldList = srcProject.getTextFieldList();
        List<ProjectFieldDO> textFieldList = project.getTextFieldList();
        // srcTextFieldList、textFieldList为空或长度为0，重新训练。 srcTextFieldList、textFieldList正常情况下不为空
        if (CollUtil.isEmpty(srcTextFieldList) || CollUtil.isEmpty(textFieldList)) {
            return true;
        }
        if (srcTextFieldList.size() != textFieldList.size()) {
            return true;
        }
        // 查看分析的字段是否有修改
        // 由于前面判断两个数组长度相等，就直接返回了，所以使用containsAll就相当于判断两个数组是否相等
        return !srcTextFieldList.containsAll(textFieldList);
    }
}
